
open Format
open Ast
open Ast_fullfill
open Ast_absolute
open Tools_identifier

module StrMap = Map.Make(String)

(*
 TODO : In absolute, remove from declared items previously in declared
*)

let print fmt ast =

	let get_value ident (map,_,_) =
		if StrMap.mem ident map then
			StrMap.find ident map
		else ident in

	let print_list func map lst = 
		ignore(List.map (fun e -> func map e) lst) in

	let rec print_list_sep func map sep = function
		| [] -> ()
		| [a] -> func map a
		| a::b -> func map a; 
		fprintf fmt "%s" sep; print_list_sep func map sep b in
	
	let rec print_program program = 
		let fullfill = fullfill_list fullfill_sourceElement {
			idents = StrSet.empty;
			labels = StrSet.empty;
			locals = StrSet.empty
			} program in
		let absolute = absolute_list absolute_sourceElement {
			abs_idents = StrSet.empty;
			abs_labels = StrSet.empty
			} StrSet.empty program in (* TODO Pourquoi StrSet.empty et pas set ? *)
		(*let map = List.fold_left (fun map ident -> free_ident map ident true) (StrMap.empty,StrSet.empty,absolute) (StrSet.elements set) in *)
		let map = StrSet.fold (fun ident map -> free_ident map ident true) fullfill.idents (StrMap.empty,StrSet.empty,absolute.abs_idents) in
		print_list print_sourceElement map program

	and print_sourceElement map = function
		| AStatement(s) -> print_statement map s
		(*| AFunctionDeclaration(funcDecl) -> print_functionDeclaration map funcDecl *) (* Modif #1 *)

	and print_statement map = function
		| ABlock(block) -> print_block map block
		| AVariableStatement(varStmt) -> print_variableStatement map varStmt
		| AConstStatement(constStmt) -> print_variableStatement map constStmt	(* Modif #2 *)
		| AFuncDeclaration(funcDecl) -> print_functionDeclaration map funcDecl	(* Modif #1 *)
		| AEmptyStatement -> ()
		| AExpressionStatement(expressionStatement) -> print_expressionStatement map expressionStatement
		| AIfStatement(ifStatement) -> print_ifStatement map ifStatement
		| AIterationStatement(iterationStatement) -> print_iterationStatement map iterationStatement
		| AContinueStatement(continueStatement) -> print_continueStatement map continueStatement
		| ABreakStatement(breakStatement) -> print_breakStatement map breakStatement
		| AReturnStatement(returnStatement) -> print_returnStatement map returnStatement
		| AWithStatement(withStatement) -> print_withStatement map withStatement
		| ALabelledStatement(labelledStatement) -> print_labelledStatement map labelledStatement
		| ASwitchStatement(switchStatement) -> print_switchStatement map switchStatement
		| AThrowStatement(throwStatement) -> print_throwStatement map throwStatement
		| ATryStatement(tryStatement) -> print_tryStatement map tryStatement
		| ADebuggerStatement -> fprintf fmt "debugger;";
	
	and print_block map block =
		 fprintf fmt "{";
		 print_list print_statement map block;
		 fprintf fmt "}";
	
	and print_variableStatement map stmt =
		fprintf fmt "var ";
		print_variableDeclarationList map stmt;
		fprintf fmt ";";
	
	and print_variableDeclarationList map (_,_,decls) =
		print_list_sep print_variableDeclaration map "," decls;

	and print_variableDeclaration map (ident,init) = match init with
		| None -> fprintf fmt "%s" (get_value ident map)
		| Some init -> fprintf fmt "%s" (get_value ident map);
			print_initializer map init

	and print_initializer map assignExpr =
		fprintf fmt "=";
		print_assignmentExpression map assignExpr
	
	and print_assignmentExpression map = function
		| ACondExpr(condExpr) -> print_conditionalExpression map condExpr
		| AAssignOp(lhs, op, assignExpr) ->
			print_leftHandSideExpression map lhs;
			fprintf fmt "%s" op;
			print_assignmentExpression map assignExpr

	and print_leftHandSideExpression map = function
		| ANewExpression(expr) -> print_newExpression map expr
		| ACallExpression(expr) -> print_callExpression map expr
	
	and print_newExpression map = function
		| AMemberExpression(expr) -> print_memberExpression map expr
		| ANewMemberExpression(expr) -> fprintf fmt "new ";
			print_newExpression map expr

	and print_callExpression map = function
		| ACallMemberArgs(expr, args) ->
			print_memberExpression map expr;
			print_arguments map args
		| ACallExpressionArgs(expr, args) ->
			print_callExpression map expr;
			print_arguments map args
		| ACallExpressionExpr(expr1, expr2) -> 
			print_callExpression map expr1;
			fprintf fmt "[";
			print_expression map expr2;
			fprintf fmt "]"
		| ACallExpressionIdent(expr, ident) ->
			print_callExpression map expr;
			fprintf fmt ".%s" ident
	
	and print_memberExpression map = function
		| APrimaryExpr(expr) -> print_primaryExpression map expr
		| AFunctionExpr(funcExpr) -> print_functionExpression map funcExpr
		| AMemberBracketExpr(expr1, expr2) -> 
			print_memberExpression map expr1;
			fprintf fmt "[";
			print_expression map expr2;
			fprintf fmt "]"
		| AMemberDotExpr(expr, ident) ->
			print_memberExpression map expr;
			fprintf fmt ".%s" ident
		| AMemberNewExpr(expr, args) ->
			fprintf fmt "new ";
			print_memberExpression map expr;
			print_arguments map args
	
	and print_arguments map args =
		fprintf fmt "(";
		print_list_sep print_assignmentExpression map "," args;
		fprintf fmt ")"
	
	and print_functionDeclaration map (_, _, _, ident, params, body) =
			fprintf fmt "function %s" (get_value ident map);
			print_functionContent map params body;
	
	and print_functionExpression map (_, ident, params, body) = match ident with
		| None -> 
			fprintf fmt "function";
			print_functionContent map params body
		| Some ident ->
			fprintf fmt "function %s" ident;
			print_functionContent map params body
	
	and print_functionContent (map,dests,_) params body = 
		let fullfill = fullfill_functionContent params body in
		let declared = (StrMap.fold (fun key _ declared -> StrSet.add key declared) map fullfill.idents) in
		let absolute = absolute_list absolute_sourceElement {
			abs_idents = StrSet.empty;
			abs_labels = StrSet.empty
			} declared body in
		(*let map = List.fold_left (fun map ident -> free_ident map ident false) (map,dests,absolute) (StrSet.elements newDeclared) in*)
		let newDeclared = StrSet.fold (fun ident set -> StrSet.remove ident set) absolute.abs_idents fullfill.idents in 
		let map = StrSet.fold (fun ident map -> free_ident map ident false) newDeclared (map,dests,absolute.abs_idents) in
		fprintf fmt "(";
		print_params map params;
		fprintf fmt "){";
		print_functionBody map body;
		fprintf fmt "}";
	
	and print_params map = function
		| [] -> ()
		| [ident] -> fprintf fmt "%s" (get_value ident map)
		| ident::params -> fprintf fmt "%s," (get_value ident map); print_params map params

	and print_functionBody map body =
		print_list print_sourceElement map body
	
	and print_expressionStatement map expr =
		print_expression map expr;
		fprintf fmt ";"
	
	and print_ifStatement map = function
		| AIfElse(expr, stmt1, stmt2) ->
			fprintf fmt "if(";
			print_expression map expr;
			fprintf fmt ")";
			print_statement map stmt1;
			fprintf fmt "else ";
			print_statement map stmt2
		| AIf(expr, stmt) ->
			fprintf fmt "if(";
			print_expression map expr;
			fprintf fmt ")";
			print_statement map stmt

	and print_iterationStatement map = function
		| ADoWhile(stmt, expr) ->
			fprintf fmt "do ";
			print_statement map stmt;
			fprintf fmt "while(";
			print_expression map expr;
			fprintf fmt ");"
		| AWhile(expr, stmt) ->
			fprintf fmt "while(";
			print_expression map expr;
			fprintf fmt ")";
			print_statement map stmt
		| AFor(expr1, expr2, expr3, stmt) ->
			fprintf fmt "for(";
			(match expr1 with
				| None -> fprintf fmt ";"
				| Some expr1 -> print_expression map expr1; fprintf fmt ";");
			(match expr2 with
				| None -> fprintf fmt ";"
				| Some expr2 -> print_expression map expr2; fprintf fmt ";");
			(match expr3 with
				| None -> ()
				| Some expr3 -> print_expression map expr3);
			fprintf fmt ")";
			print_statement map stmt
		| AForIn(lhs, expr, stmt) ->
			fprintf fmt "for(";
			print_leftHandSideExpression map lhs;
			fprintf fmt " in ";
			print_expression map expr;
			fprintf fmt ")";
			print_statement map stmt
		| AForVar(is_meta, varDecls, expr1, expr2, stmt) ->
			fprintf fmt "for(var ";
			print_variableDeclarationList map (false,is_meta,varDecls);
			fprintf fmt "; ";
			(match expr1 with
				| None -> fprintf fmt ";"
				| Some expr1 -> print_expression map expr1; fprintf fmt ";");
			(match expr2 with
				| None -> ()
				| Some expr2 -> print_expression map expr2);
			fprintf fmt ")";
			print_statement map stmt
		| AForVarIn(_, varDecl, expr, stmt) ->
			fprintf fmt "for(var ";
			print_variableDeclaration map varDecl;
			fprintf fmt " in ";
			print_expression map expr;
			fprintf fmt ")";
			print_statement map stmt
	
	and print_continueStatement map = function
		| None -> fprintf fmt "continue;"
		| Some ident -> 
			fprintf fmt "continue %s;" (get_value ident map)

	and print_breakStatement map = function
		| None -> fprintf fmt "break;"
		| Some ident -> fprintf fmt "break %s;" (get_value ident map)

	and print_returnStatement map = function
		| None -> fprintf fmt "return;";
		| Some expr -> fprintf fmt "return ";
			print_expression map expr;
			fprintf fmt ";"

	and print_withStatement map (expr, stmt) = 
		fprintf fmt "with(";
		print_expression map expr;
		fprintf fmt ")";
		print_statement map stmt
	
	and print_labelledStatement map (ident, stmt) =
		fprintf fmt "%s:" (get_value ident map);
		print_statement map stmt

	and print_switchStatement map (expr, caseBlock) = 
		fprintf fmt "switch(";
		print_expression map expr;
		fprintf fmt ")";
		print_caseBlock map caseBlock
	
	and print_caseBlock map = function
		| ACasesNoDefault(caseClauses) -> 
			fprintf fmt "{";
			print_caseClauses map caseClauses;
			fprintf fmt "}"
		| ACasesDefault(caseClauses1, defaultClause, caseClauses2) ->
			fprintf fmt "{";
			print_caseClauses map caseClauses1;
			print_defaultClause map defaultClause;
			print_caseClauses map caseClauses2;
			fprintf fmt "}"

	and print_defaultClause map stmts =
		fprintf fmt "default:";
		print_list print_statement map stmts

	and print_caseClauses map clauses = 
		print_list print_caseClause map clauses;
		
	and print_caseClause map (expr,stmts) =
		fprintf fmt "case ";
		print_expression map expr;
		fprintf fmt " :";
		print_list print_statement map stmts
	
	and print_throwStatement map expr =
		fprintf fmt "throw ";
		print_expression map expr;
		fprintf fmt ";"
	
	and print_tryStatement map = function
		| ATryCatch(block, catch) ->
			fprintf fmt "try ";
			print_block map block;
			print_catch map catch
		| ATryFinally(block, finally) ->
			fprintf fmt "try ";
			print_block map block;
			print_finally map finally
		| ATryCatchFinally(block, catch, finally) ->
			fprintf fmt "try ";
			print_block map block;
			print_catch map catch;
			print_finally map finally
	
	and print_catch map (ident,block) =
		fprintf fmt "catch (%s)" (get_value ident map);
		print_block map block;
	
	and print_finally map block =
		fprintf fmt "finally ";
		print_block map block
	
	and print_conditionalExpression map = function
		| ACondBinaryExpr(expr) -> print_binaryExpression map expr
		| ACondTernaryExpr(expr, assignExpr1, assignExpr2) ->
			print_binaryExpression map expr;
			fprintf fmt "?";
			print_assignmentExpression map assignExpr1;
			fprintf fmt ":";
			print_assignmentExpression map assignExpr2
	
	and print_binaryExpression map = function
		| AUnaryExpr(unaryExpression) -> print_unaryExpression map unaryExpression
		| ABinaryExpr(op, expr1, expr2) ->
			print_binaryExpression map expr1;
			let op = (if Str.string_match (Str.regexp "[a-z]+") op 0 then " " ^ op ^ " " else op) in
			fprintf fmt "%s" op;
			print_binaryExpression map expr2
	
	and print_unaryExpression map = function
		| APostfixExpr(expr) -> print_postfixExpression map expr;
		| AOpUnaryExpr(op, expr) -> fprintf fmt "%s%s" op (if Str.string_match (Str.regexp "[a-z]+") op 0 then " " else "");
		print_unaryExpression map expr
	
	and print_postfixExpression map = function
		| ALeftHandSide(lhs) -> print_leftHandSideExpression map lhs
		| ALeftHandSideOp(lhs, op) ->
			print_leftHandSideExpression map lhs;
			fprintf fmt "%s" op
	
	and print_expression map expr =
		print_list_sep print_assignmentExpression map "," expr
	
	and print_primaryExpression map = function
		| AThis -> fprintf fmt "this"
		| AIdentifier(identifier) -> fprintf fmt "%s" (get_value identifier map)
		| ALiteral(literal) -> print_literal literal
		| AArrayLiteral(arrayLiteral) -> print_arrayLiteral map arrayLiteral
		| AObjectLiteral(objectLiteral) -> print_objectLiteral map objectLiteral
		| AExpression(expr) -> fprintf fmt "("; print_expression map expr; fprintf fmt ")"

	and print_literal = function
		| ANull -> fprintf fmt "null"
		| ABooleanLiteral(boolean) -> fprintf fmt "%s" (if boolean then "true" else "false")
		| ALitNumericLiteral(numeric) -> fprintf fmt "%s" numeric
		| ALitStringLiteral(str) -> fprintf fmt "\"%s\"" ((Str.global_replace (Str.regexp_string "\"") "\\\"") str)
		| ARegularExpressionLiteral(regexp) -> print_regularExpression regexp
	
	and print_regularExpression (str1, str2) = 
		fprintf fmt "/%s/%s" ((Str.global_replace (Str.regexp_string "/") "\\/") str1) str2

	and print_arrayLiteral map = function
		| AElisions(elisions) -> fprintf fmt "[";
			print_elisions elisions;
			fprintf fmt "]"
		| AElements(elements) -> fprintf fmt "[";
			print_elements map elements;
			fprintf fmt "]"
		| AElementsAndElisions(elements, elisions) ->
			fprintf fmt "[";
			print_elements map elements;
			print_elisions elisions;
			fprintf fmt "]"
	
	and print_elisions = function
		| [] -> ()
		| a::b -> fprintf fmt ","; print_elisions b
	
	and print_elements map elements =
		print_list_sep print_element map "," elements
	
	and print_element map (elisions, expr) =
		print_elisions elisions;
		print_assignmentExpression map expr
	
	and print_objectLiteral map objectLiteral =
		fprintf fmt "{" ;
		print_list_sep print_propertyAssignment map "," objectLiteral;
		fprintf fmt "}"
	
	and print_propertyName = function
		| AIdentifierName(str) -> fprintf fmt "%s" str
		| AStringLiteral(str) -> fprintf fmt "\"%s\"" ((Str.global_replace (Str.regexp_string "\"") "\\\"") str)
		| ANumericLiteral(numeric) -> fprintf fmt "%s" numeric

	and print_propertyAssignment map = function
		| APropAssignment(propertyName, assignExpr) -> 
			print_propertyName propertyName;
			fprintf fmt ":";
			print_assignmentExpression map assignExpr
		| APropGet(propertyName, functionBody) ->
			fprintf fmt "get ";
			print_propertyName propertyName;
			print_functionContent map [] functionBody
			
		| APropSet(propertyName, idents, functionBody) ->
			fprintf fmt "set ";
			print_propertyName propertyName;
			print_functionContent map idents functionBody
	in
		print_program ast

